package com.linkoog.devtools.raw_index;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.util.indexing.FileBasedIndex;
import com.intellij.util.indexing.FileBasedIndexImpl;
import com.linkoog.devtools.raw_index.task.DeleteIndexTask;
import com.linkoog.devtools.raw_index.task.UpdateIndexTask;
import com.linkoog.devtools.utils.PsiClassUtils;
import com.linkoog.devtools.utils.VirtualFileUtils;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;


public class PsiRawFileIndexChangeListener extends UserDataHolderBase implements BulkFileListener, Disposable {

    private static final Logger log = Logger.getInstance(PsiRawFileIndexChangeListener.class);

    private static final boolean delayEnable = true;
    private static final long delayTime = 150;

    private final Project project;

    private final ReentrantLock lock = new ReentrantLock();

    private volatile AtomicBoolean isRunning = new AtomicBoolean(false);

    private final LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

    private final ThreadPoolExecutor executor = ThreadPoolExecutorUtils.createThreadPoolExecutor(Integer.MAX_VALUE, 3);

    public PsiRawFileIndexChangeListener(Project project) {
        this.project = project;
    }

    @Override
    public void before(@NotNull List<? extends @NotNull VFileEvent> events) {
        for (VFileEvent event : events) {
            final VirtualFile virtualFile = event.getFile();
            if( virtualFile == null) continue;

            if (event instanceof VFileDeleteEvent) {
                //删除事件
                final List<PsiRawFileIndex> list = PsiRawFileIndex.EXTENSION_POINT_NAME.getExtensions(project);
                if (list.isEmpty()) return;

                for (PsiRawFileIndex psiRawFileIndex : list) {
                    psiRawFileIndex.afterFileDelete(virtualFile);
                }
            }
        }
    }

    /**
     *   文件编辑保存后，触发该事件
     */
    @Override
    public void after(@NotNull List<? extends @NotNull VFileEvent> events) {
        for (VFileEvent event : events) {
            final VirtualFile virtualFile = event.getFile();
            if(virtualFile == null || virtualFile.isDirectory()) continue;


            //忽略-->系统配置文件
            if (isSystemConfigFile(virtualFile)) continue;
            //忽略-->编译输出文件
            if (isBuildOutputFile(virtualFile)) continue;
            //忽略-->项目配置文件
            if (isProjectConfigFile(virtualFile)) continue;




            //触发更新事件
            if (event instanceof VFileCreateEvent){
                //新建文件，存在解析时,psiIndex还没创建问题
                Key<Long> cacheUpdateTimeKey = Key.create(virtualFile.getName() + "#" + virtualFile.hashCode());
                long fileUpdateTime = VirtualFileUtils.getTimestamp(virtualFile);

                UpdateIndexTask updateIndexTask = new UpdateIndexTask(project, this, virtualFile, cacheUpdateTimeKey, fileUpdateTime);
                submitTask(updateIndexTask);
            }else if (event.isFromSave()  || event instanceof VFileContentChangeEvent){
                //更新缓存
                long fileUpdateTime = VirtualFileUtils.getTimestamp(virtualFile);
                Key<Long> cacheUpdateTimeKey = Key.create(virtualFile.getName() + "#" + virtualFile.hashCode());
                Long indexUpdateTime = PsiRawFileIndexChangeListener.this.getUserData(cacheUpdateTimeKey);
                if (indexUpdateTime == null){
                    indexUpdateTime = 0L;
                }

                //距离上次更新，没有变化
                if (fileUpdateTime == indexUpdateTime){
                    return;
                }

                Key<Long> lastFileUpdateTimeKey = Key.create(virtualFile.getName() + "#" + virtualFile.hashCode() + "#edit");
                Long lastFileUpdateTime = PsiRawFileIndexChangeListener.this.getUserData(lastFileUpdateTimeKey);
                if (lastFileUpdateTime == null){
                    lastFileUpdateTime = 0L;
                }

                //延后更新，避免在编辑中的时候，更新太频繁
                PsiRawFileIndexChangeListener.this.putUserData(lastFileUpdateTimeKey, fileUpdateTime);
                if (delayEnable && ((fileUpdateTime - lastFileUpdateTime) < delayTime || (fileUpdateTime - indexUpdateTime) < delayTime) ){
                    return;
                }

                UpdateIndexTask updateIndexTask = new UpdateIndexTask(project, this, virtualFile, cacheUpdateTimeKey, fileUpdateTime);
                submitTask(updateIndexTask);
            } else if (event instanceof VFileDeleteEvent) {
                long fileUpdateTime = VirtualFileUtils.getTimestamp(virtualFile);
                Key<Long> cacheUpdateTimeKey = Key.create(virtualFile.getName() + "#" + virtualFile.hashCode());
                DeleteIndexTask deleteIndexTask = new DeleteIndexTask(project, this, virtualFile, cacheUpdateTimeKey, fileUpdateTime);
                submitTask(deleteIndexTask);
            }
        }
    }

    /**
     *   virtualFile 文件内容变化，更新索引
     */
    private void submitTask(Runnable task){
        try {
            taskQueue.put(task);
        }catch (Throwable ignored){
        }

        if (! isRunning.get()){
            try {
                isRunning.compareAndSet(false, true);
                executeUpdateIndexTasks();
            }catch (Throwable ignored){
            }
        }

        /*ApplicationManager.getApplication().executeOnPooledThread(() ->{

        });*/
    }


    private void executeUpdateIndexTasks(){
        ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().run(new Task.Backgroundable(project, "DevTools fileIndex Update..."){
            @Override
            public void run(@NotNull ProgressIndicator progressIndicator) {
                try {
                    long start = System.currentTimeMillis();
                    long taskNum = 0;
                    List<CompletableFuture<Void>> futures = new ArrayList<>();
                    while (true) {
                        Runnable task = taskQueue.poll(50, TimeUnit.MICROSECONDS);
                        if (task == null) {
                            break;
                        }

                        CompletableFuture<Void> future = CompletableFuture.runAsync(task, executor);
                        futures.add(future);
                        taskNum++;
                    }

                    CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
                    // 等待所有 CompletableFuture 完成
                    allFutures.join();

                    log.info("DevTools fileIndex Update, taskNum: " + taskNum + ", cost time:  " + (System.currentTimeMillis() - start) + " ms");
                } catch (Throwable ignored) {
                } finally {
                    isRunning.compareAndSet(true, false);
                }
            }
        }));
    }


    @Override
    public void dispose() {

    }

    /**
     *  是否为模块的构建输出的文件
     */
    private boolean isBuildOutputFile(VirtualFile virtualFile){
        if (virtualFile.isInLocalFileSystem()) {
            Module module = ReadAction.compute(() -> ProjectFileIndex.getInstance(project).getModuleForFile(virtualFile));
            if (module != null) {
                CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
                if (extension != null) {
                    VirtualFile outputPath = extension.getCompilerOutputPath();
                    return outputPath != null && VfsUtilCore.isAncestor(outputPath, virtualFile, false);
                }
            }
        }
        return false;
    }

    private boolean isProjectConfigFile(VirtualFile virtualFile){
        if (virtualFile.isInLocalFileSystem()) {
            VirtualFile projectFile = project.getProjectFile();
            if (projectFile != null){
                VirtualFile projectConfig = projectFile.getParent();
                return projectConfig != null && VfsUtilCore.isAncestor(projectConfig, virtualFile, false);
            }
        }
        return false;
    }

    private boolean isSystemConfigFile(VirtualFile virtualFile){
        if (virtualFile.isInLocalFileSystem()) {
            String configPath = PathManager.getConfigPath();
            return virtualFile.getPath().startsWith(configPath);
        }
        return false;
    }



}